/**************************************************************************************
 *  Copyright (c)  2010  Leszek Karcz.
 *  Permission is granted to copy, distribute and/or modify this document
 *  under the terms of the GNU Free Documentation License, Version 1.3
 *  or any later version published by the Free Software Foundation;
 *  with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts.
 *  A copy of the license is included in the section entitled "GNU
 *  Free Documentation License". 
 **************************************************************************************/
package data;
import data.LinkedList;
import data.tileables.*;

/**************************************************************************************
 * Author(s): Leszek Karcz
 * This class is a structure for housing and accessing tiles and path-finding
 **************************************************************************************/
public class Map extends HexMap
{
	private Tile[][] tiles;
	
	/**************************************************************************************
	 * Author(s): Leszek Karcz
	 * Map constructor
	 **************************************************************************************/
	public Map(int[][] mapArray)
	{
		this.setHeight(mapArray.length);
		this.setWidth(mapArray[0].length);
		
		this.tiles = new Tile[this.getHeight()][this.getWidth()];
		for(int i=0; i<this.getHeight(); i++)
		{
			for(int j=0; j<this.getWidth(); j++)
			{
				this.tiles[i][j]=new Tile(i,j,mapArray[i][j]);
			}
		}		
	}
	
	/**************************************************************************************
	 * Author(s): Leszek Karcz
	 * Returns tile at [x,y] coordinates
	 **************************************************************************************/
	public Tile getTile(int x, int y)
	{
		return this.tiles[y][x];
	}	
	
	/**************************************************************************************
	 * Author(s): Leszek Karcz
	 * Returns tile terrain at [x,y] coordinates
	 **************************************************************************************/
	public int getTerrainAt(int x, int y)
	{
		return this.tiles[y][x].getTerrain();
	}	
	
	/**************************************************************************************
	 * Author(s): Leszek Karcz
	 * Returns tile terrain at [x,y] coordinates
	 **************************************************************************************/
	public String getDecalImageAt(int x, int y)
	{
		return this.tiles[y][x].getDecalImage();
	}	
	
	/**************************************************************************************
	 * Author(s): Leszek Karcz
	 * returns top controllable of certain display level on tile at [x,y] coordinates
	 **************************************************************************************/	
	public Controllable getTopControllableAt(int x, int y, int displayLevel)
	{
		return this.tiles[y][x].getTopControllable(displayLevel);
	}
	
	/**************************************************************************************
	 * Author(s): Leszek Karcz
	 * returns list of all units on tile at [x,y] coordinates
	 **************************************************************************************/	
	public LinkedList<Unit> getUnitsAt(int x, int y)
	{
		return this.tiles[y][x].getUnits();
	}
	
	/**************************************************************************************
	 * Author(s): Leszek Karcz
	 * returns structure on tile at [x,y] coordinates
	 **************************************************************************************/	
	public Structure getStructureAt(int x, int y)
	{
		return this.tiles[y][x].getStructure();
	}
	
	/**************************************************************************************
	 * Author(s): Leszek Karcz
	 * returns true if there is a structure at [x,y] coordinates
	 **************************************************************************************/	
	public boolean hasStructureAt(int x, int y)
	{
		return this.tiles[y][x].hasStructure();
	}
	
	/**************************************************************************************
	 * Author(s): Leszek Karcz
	 * returns true if the tile at [x,y] coordinates is passable
	 **************************************************************************************/	
	public boolean isPassableAt(int x, int y, int playerID)
	{
		return this.tiles[y][x].isPassable(playerID);
	}
	
	/**************************************************************************************
	 * Author(s): Leszek Karcz
	 * returns resource object at [x,y] coordinates
	 **************************************************************************************/	
	public Resources getResourcesAt(int x, int y)
	{
		return this.tiles[y][x].getResources();
	}
	
	/**************************************************************************************
	 * Author(s): Leszek Karcz
	 * returns top item at [x,y] coordinates
	 **************************************************************************************/	
	public Item getTopItemAt(int x, int y)
	{
		return this.tiles[y][x].getTopItem();
	}
	
	/**************************************************************************************
	 * Author(s): Leszek Karcz
	 * places the controllable on the map
	 **************************************************************************************/	
	public boolean putControllableOnTile(int x, int y, Controllable controllable)
	{
		int oldX=controllable.getX();
		int oldY=controllable.getY();
	
		if(this.verifyPosition(x, y))
		{
			boolean flag=this.tiles[y][x].putControllable(controllable);
			
			if(flag)
			{
				controllable.setPosition(x, y);
				if(controllable.isAlive())
				{
					
					if(this.verifyPosition(oldX, oldY))
					{
						this.tiles[oldY][oldX].removeControllable(controllable);
					}	
				}
				else
				{
					if(this.verifyPosition(x, y))
					{
						this.tiles[y][x].removeControllable(controllable);
					}	
				}

				return true;
			}
				
		}
		return false;
	}
	
	/**************************************************************************************
	 * Author(s): Leszek Karcz
	 * places the doodad on the map
	 **************************************************************************************/	
	public void putDoodadOnTile(int x, int y, Doodad doodad)
	{
		int oldX=doodad.getX();
		int oldY=doodad.getY();
		if(this.verifyPosition(oldX, oldY))
		{
			this.tiles[oldY][oldX].removeDoodad(doodad);
		}
		if(this.verifyPosition(x, y))
		{
			this.tiles[y][x].putDoodad(doodad);
			doodad.setPosition(x, y);
		}
	}
	
	/**************************************************************************************
	 * Author(s): Leszek Karcz
	 * moves the controllable according to the path
	 **************************************************************************************/
	public LinkedList<Integer> moveControllable(Controllable controllable, int x, int y, LinkedList<Integer> path)
	{
		if(this.verifyPosition(x, y) && this.tiles[y][x].isPassable(controllable.getPlayerID()))
		{
			if(path==null)
			{
				path=this.calculatePath(controllable.getX(), controllable.getY(), x, y, controllable.getPlayerID());
				if((path!=null) && (path.size>0))
				{
					Integer direction=path.removeFirst();
					if(controllable instanceof Unit)
					{
						((Unit)controllable).setDirection(direction);
					}
					int[] pos=this.getAdjacentPosition(controllable.getX(), controllable.getY(), direction);
					this.removeControllable(controllable);
					this.putControllableOnTile(pos[0], pos[1], controllable);
					return path;
				}
				else
				{
					return null;
				}
				
			}
			else
			{
				if(path.getSize()!=0)
				{
					Integer direction=path.removeFirst();
					if(controllable instanceof Unit)
					{
						((Unit)controllable).setDirection(direction);
					}
					int[] pos=this.getAdjacentPosition(controllable.getX(), controllable.getY(), direction);
					if(this.tiles[pos[1]][pos[0]].isPassable(controllable.getPlayerID()))
					{
						this.removeControllable(controllable);
						putControllableOnTile(pos[0], pos[1], controllable);
						return path;
					}
					else
					{
						direction=path.removeFirst();
						if(controllable instanceof Unit)
						{
							((Unit)controllable).setDirection(direction);
						}
						path=this.calculatePath(controllable.getX(), controllable.getY(), x, y, controllable.getPlayerID());
						pos=this.getAdjacentPosition(controllable.getX(), controllable.getY(), direction);
						this.removeControllable(controllable);
						putControllableOnTile(pos[0], pos[1], controllable);
						return path;						
					}					
				}
				else
				{
					return null;
				}				
			}
		}
		else
		{
			return null;
		}
	}
	
	
	/**************************************************************************************
	 * Author(s): Changmin Bae
	 * removes Controllable from tile at x,y
	 **************************************************************************************/
	public void removeControllable(Controllable c)
	{
		if(this.verifyPosition(c.getX(), c.getY()))
		{
		tiles[c.getY()][c.getX()].removeControllable(c);
		}
		c.setPosition(-1, -1);
	}

	/**************************************************************************************
	 * Author(s): Yan Santos
	 * finds path
	 **************************************************************************************/	
	
	public LinkedList<Integer> calculatePath(int xFrom, int yFrom, int xTo, int yTo, int playerID)
	{
		LinkedList<Integer> directionQueue = new LinkedList<Integer>();
		int w = getWidth();
		int numOfElem = (getWidth())*(getHeight());
		s = new int[numOfElem];
		g = new int[numOfElem];
		for(int i = 0; i < numOfElem; i++)
		{
			s[i] = -1;
			g[i] = -1;
			//this.verifyPosition(i/w, i%w) && 
			boolean passability = tiles[i/w][i%w].isPassable(playerID);
			// todo: when fog of war is applied, add another condition to
			// check if fog of war is lifted
			if(passability==false) s[i] = -2;
			if(i%w == xFrom && i/w == yFrom) { ss = i; }
			if(i%w == xTo && i/w == yTo) { gg = i; }
		}
		a = gg;
		b = gg;
		int [] sorted = new int[7];
		while(a != ss)
		{	
			sorted = manhattanSort();
			sortedAdd(sorted);
			/*if(symm(a)==0)
			{
				add(a,1-w);
				add(a,-1-w);
				add(a,-w);	
				add(a,-1);
				add(a,1);
				add(a,w);
			}
			else
			{
				add(a,1+w);
				add(a,-1+w);
				add(a,w);
				add(a,-1);
				add(a,1);
				add(a,-w);
			}*/
			a=s[a];			
		}
		while(a != gg)
		{
			directionQueue.addBack(sv(g[a]));
			a+=g[a];
		}		
		return directionQueue;
	}
	private int ss, gg, a, b;
	private int [] s, g;
	private int add(int p, int o)
	{
		int temp = s[p+o];
		if(temp == -1)
		{
			s[b] = p+o;
			b = p+o;
			g[b] = -o;
		}
		return temp;
	}
	private int sv(int o)
	{
		int temp = 0;
		if(o==1)
		{
			if(symm(a)==1) //downright
				temp = 1;
			else temp = 2; //upright
		}
		if (o==getWidth())//up
			temp = 3;
		if (o == getWidth()-1) //topleft
			temp = 4;
		if (o == getWidth()+1) //upright
			temp = 2;
		if(o==-1)
		{
			if(symm(a)==0) //downleft
				temp = 4;
			else temp = 5;	//topleft			
		}
		if(o == -getWidth())//down
			temp = 0;
			
		if(o == -getWidth()-1) //downleft
			temp = 5;
		if(o == -getWidth()+1) //downright
			temp = 1;
		return temp;
	}
	//actually distance formula not Manhattan
	private double manhattanDistance(int a, int ss)
	{
		double temp = 0;
		int w = getWidth();
		temp = Math.sqrt(Math.pow((a%w - ss%w),2)+ Math.pow((a/w - ss/w),2));
		return temp;
	}
	//actually distance formula not Manhattan
	private int [] manhattanSort()
	{
		double[] temp = new double[6];
		int [] index = new int[7];
		int w = getWidth();
		if(symm(a%w)==1)
		{
			temp[0] = manhattanDistance(a+1+w,ss);//se
			temp[1] = manhattanDistance(a-1+w,ss);//sw
			temp[2] = manhattanDistance(a+0+w,ss);//s
			temp[3] = manhattanDistance(a-1-0,ss);//w
			temp[4] = manhattanDistance(a+1-0,ss);//e
			temp[5] = manhattanDistance(a+0-w,ss);//n
			index[6] = 0;
			/*add(a,1-w);
			add(a,-1-w);
			add(a,-w);	
			add(a,-1);
			add(a,1);
			add(a,w);*/
		}
		else
		{
			temp[0] = manhattanDistance(a+1-w,ss);//ne
			temp[1] = manhattanDistance(a+0-w,ss);//n
			temp[2] = manhattanDistance(a-1-w,ss);//nw
			temp[3] = manhattanDistance(a-1-0,ss);//w
			temp[4] = manhattanDistance(a+0+w,ss);//s
			temp[5] = manhattanDistance(a+1-0,ss);//e
			index[6] = 1;
			/*add(a,1+w);
			add(a,w);
			add(a,-1+w);
			add(a,-1);
			add(a,-w);
			add(a,1);*/
		}
		double[] copy = new double[6];
		copy = temp.clone();
		for(int i = 0; i < temp.length-1; i++)
		{
			for(int j = i+1; j < temp.length; j++)
			{
				double mid;
				if(temp[i]>temp[j])
				{
					mid = temp[j];
					temp [j] = temp [i];
					temp [i] = mid;
				}
			}
		}
		for(int i = 0; i < index.length-1; i++)
		{
			for(int j = 0; j < index.length-1; j++)
			{
				//index[i] = j;
				if(temp[i]==copy[j])
					index[i] = j+1;
			}
		}
		return index;
	}
	private void sortedAdd(int [] index)
	{
		int w = getWidth();
		if(index[6]==1)
		{
			for(int i = 0; i < index.length-1; i++)
			{
				switch(index[i])
				{
				case 1:	add(a,1-w); break;
				case 2:	add(a,-1-w); break;
				case 3:	add(a,-w); break;	
				case 4:	add(a,-1); break;
				case 5:	add(a,1); break;
				case 6:	add(a,w); break;
				default: break;
				}
			}			
		}
		else
		{
			for(int i = 0; i < index.length-1; i++)
			{
				switch(index[i])
				{
				case 1:	add(a,1+w); break;
				case 2:	add(a,w); break;
				case 3:	add(a,-1+w); break;	
				case 4:	add(a,-1); break;
				case 5:	add(a,-w); break;
				case 6:	add(a,1); break;
				default: break;
				}
			}
		}
	}
	private int symm(int a)
	{
		int symm = a%getWidth();
		symm %= 2;
		return symm;
	}
}
